home *** CD-ROM | disk | FTP | other *** search
Text File | 1998-08-10 | 11.8 KB | 394 lines | [TEXT/CWIE] |
- /*************************************************************************************
- TTileGrid.cp
-
- A TileGrid is a class that describes a 2 dimensional array of tiles that can be
- drawn as the background for a game. This current version only supplies
- graphics routines, but collision and other logic should be fairly easy to
- generate.
-
- Author: Timothy Carroll
- Apple Developer Technical Support
- timc@apple.com
-
- Modification History:
-
- 4/2/97 TMC fTileData is a handle, but the destructor was calling DisposePtr on it.
- Ouch! Bad programmer, no donut. Because my handle was cast to a **CellGridType, I had
- to recast it back when I disposed of it, and I did it wrong. Spotlight caught this
- one for me.
- 1/23/97 TMC Added include for Moofwars.h so that MrC will compile
- 8/15/96 TMC Initial Release
-
- Copyright © 1996, 1997 Apple Computer, Inc., All Rights Reserved
-
- You may incorporate this sample code into your applications without
- restriction, though the sample code has been provided "AS IS" and the
- responsibility for its operation is 100% yours. However, what you are
- not permitted to do is to redistribute the source as "DSC Sample Code"
- after having made changes. If you're going to re-distribute the source,
- we require that you make it clear in the source that the code was
- descended from Apple Sample Code, but that you've made changes.
- *************************************************************************************/
- #include <Memory.h>
- #include <Resources.h>
- #include "Moofwars.h"
- #include "TTileGrid.h"
- #include "scaling.h"
-
-
-
-
- /*************************************************************************************
- TTileGrid::TTileGrid
- ~TTileGrid::TTileGrid
- *************************************************************************************/
-
- TTileGrid::TTileGrid (void)
- {
- fWidth = 0;
- fHeight = 0;
- fDefaultTile = 0;
- fTiles = NULL;
- fTileData = NULL;
-
- }
-
- TTileGrid::~TTileGrid (void)
- {
- if (fTiles != NULL)
- DisposeTileCollection (fTiles);
- if (fTileData != NULL)
- DisposeHandle ((Handle) fTileData);
- }
-
- /*************************************************************************************
- TTileGrid::CreateGridFromResource
- *************************************************************************************/
- OSErr
- TTileGrid::CreateGridFromResource (SInt16 resID)
- {
- OSErr theErr = noErr;
- TileGridResHeader **grid;
- Ptr srcPtr, destPtr;
-
- grid = (TileGridResHeader **) Get1Resource (TileGridResType, resID);
- theErr = ResError();
- FAIL_OSERR (theErr,"\pFailed to load GRID resource")
- FAIL_NIL (grid, "\pFailed to load GRID resource")
-
- if ((**grid).version != 0) SIGNAL_ERROR ("\pUnable to read this GRID format")
-
- fWidth = (**grid).width;
- fHeight = (**grid).height;
- fDefaultTile = (**grid).defaultTile;
- fTiles = NewTileCollection ((**grid).tileResID);
- FAIL_NIL (fTiles, "\pFailed to load the Tiles needed for GRID.")
- fTiles->LockCollection();
-
- fTileData = (CellGridType **) NewHandle (fWidth * fHeight * sizeof (CellGridType));
- theErr = MemError();
- FAIL_OSERR (theErr, "\pFailed to allocate memory for the GRID data")
- FAIL_NIL (fTileData, "\pFailed to allocate memory for the GRID data")
-
- destPtr = (Ptr) *fTileData;
- srcPtr = (Ptr) (*grid)+sizeof (TileGridResHeader);
-
- BlockMoveData (srcPtr, destPtr, fWidth * fHeight * sizeof (CellGridType));
-
- goto cleanup;
-
- error:
-
- if (theErr == noErr)
- theErr = paramErr;
-
- cleanup:
-
- if (grid != NULL)
- ReleaseResource ((Handle) grid);
-
- return theErr;
-
- }
-
-
- /*************************************************************************************
- Accessor Functions
- *************************************************************************************/
-
- CellGridType
- TTileGrid::GetGridValue (SInt32 h, SInt32 v)
- {
- SInt32 offset;
- if ((h < 0) || (h >= fWidth) || (v < 0) || (v >= fHeight))
- return fDefaultTile;
-
- offset = (v*fHeight + h);
- return *((*fTileData)+offset);
- }
-
- void
- TTileGrid::SetGridValue (SInt32 h, SInt32 v, CellGridType value)
- {
- SInt32 offset;
-
- if ((h < 0) || (h >= fWidth) || (v < 0) || (v >= fHeight))
- return;
-
- offset = (v*fHeight + h);
-
- *((*fTileData)+offset) = value;
- }
-
- /*************************************************************************************
- TTileGrid::DrawGrid
-
- The heart of the current TTileGrid code. This routine uses the values stored in the grid to draw
- into a rectangle on the screen.
-
- Inputs:
-
- We input a rectangle (in screen coordinates) to draw to, and the top left corner (in world coordinates)
- that corresponds to the top left corner of the rectangle. This routine figures out which tiles need to be
- drawn and draws them as quickly as possible.
-
-
- Further optimizations:
- In our worse case, we clip both vertically and horizontally. A normal unclipped tile grid would be 20x15
- for a 640x480 screen, or 300 tiles. The fully clipped case would be 21x16 or 336 tiles. Of these tiles,
- 70 of them will be drawn with the clipped algorithm or about 20%. So speeding up the handling of clipped
- tiles would improve the speed of grid drawing in most cases.
-
- The best way to speed up clipping is probably to differentiate between vertical and horizontal clipping.
- If we are just vertical clipping, we can use the same fast inner blit loops that we've been using for
- the unclipped blitters -- we just draw less rows. Horizontal clipping can use the fact that we always
- have a set of data aligned to the same alignment as the destination.
-
- As mentioned below, there is one switch that can be moved from once per tile to once per grid. The main
- reason this isn't done in the code below is that I haven't found a clean C++ way to do it that doesn't
- involve copying the same loop 8 times. I figured that clarity was better in this case, because the
- performance difference was negligible.
- *************************************************************************************/
-
- void
- TTileGrid::DrawGrid (Rect *screenRect, SInt32 topGlobal, SInt32 leftGlobal)
- {
- SInt32 tilesWidth, tilesHeight; // number of tiles in each direction to draw.
- SInt32 indexH, indexV; // index coordinates of the tile currently being drawn
- SInt32 startH; // base index to start each line at
- SInt32 drawY, drawX; // screen coordinates of the tile currently being drawn
- SInt32 startX; // base x coordinate to start each line of tiles at.
- Boolean horizontalClip, verticalClip; // whether or not we need to clip
-
- SInt32 hLoop, vLoop; // used to loop over the tiles being drawn
-
- unsigned char *destPtr; // Points to the destination inside the buffer
- unsigned char *destRowPtr; // Points to the beginning of the line in a buffer
- UInt32 vertOffset; // Use this to increment destRowPtr to the next line of tiles
- UInt32 alignment; // we can calculate the alignment for all the tiles, which
- // is their exact byte alignment. This lets us call a custom
- // blitter for that alignment. See the TTileCollection
- // blitters for more info.
-
- SInt32 temp;
-
-
- // ASSERTIONS
- // The screenRectangle should start on a multiple of 8 bytes, and should be an integral number of tiles
- // tall and wide. This helps us determine exactly which tiles to draw.
-
- #if qDebugging
- if (screenRect->left % 8 != 0)
- {
- DebugStr ("\pscreenRect improperly aligned");
- return;
- }
-
- if ((screenRect->right - screenRect->left) % 32 != 0)
- {
- DebugStr ("\pscreenRect improper width");
- return;
- }
-
- if ((screenRect->bottom - screenRect->top) % 32 != 0)
- {
- DebugStr ("\pscreenRect improper width");
- return;
- }
- #endif
-
- // First, determine the number of tiles in the screen rectangle.
-
- tilesWidth = (screenRect->right - screenRect->left) >> 5;
- tilesHeight = (screenRect->bottom - screenRect->top) >> 5;
-
- // We need to determine where we actually need to start drawing, both in screen coordinates and in
- // indices into the Grid values. We also determine if we actually need to perform any clipping.
- //
- // If the top/left coordinate in world coordinates is a multiple of 32, then we are exactly aligned to
- // the screen rectangle, and no clipping needs to take place. This is actually the fastest case, because
- // we don't call our slower clipping tiles, and we also call the fastest tile blitter.
- //
- // If it isn't a multiple of 32, then we're not aligned and we need to draw an extra tile to get the
- // full row. Because we handle the clipped case separately, we actually decrement the number of tiles
- // we'll be drawing.
-
- temp = leftGlobal % 32;
- startH = leftGlobal / 32;
- if (temp != 0)
- {
- tilesWidth--;
- horizontalClip = true;
-
- if (leftGlobal < 0)
- {
- startH = (leftGlobal-31) / 32;
- if (temp < 0) temp+=32;
- }
- }
- else
- horizontalClip = false;
-
- // However much our start point is offset to the right, we need to offset that far to the left to
- // determine our initial drawing point.
- startX = screenRect->left - temp;
-
-
- // We do the same for the vertical components.
-
- temp = topGlobal % 32;
- indexV = topGlobal / 32;
- if (temp != 0)
- {
- tilesHeight--;
- verticalClip = true;
-
- if (topGlobal < 0)
- {
- indexV = (topGlobal-31) /32;
- if (temp < 0) temp+= 32;
- }
- }
- else
- verticalClip = false;
-
- drawY = screenRect->top - temp;
-
- // Calculate our initial destination pointer and our offset between rows. We also
- // calculate the alignment for the entire grid.
-
- destRowPtr = gDestBaseAddr + drawY * gRowBytes + startX;
- vertOffset = gRowBytes << 5;
- alignment = ((long) destRowPtr) & 0x07;
-
- // handle top clip
- if (verticalClip)
- {
- drawX = startX;
- indexH = startH;
-
- if (horizontalClip)
- temp = tilesWidth+2;
- else
- temp = tilesWidth;
-
- for (hLoop = 0; hLoop < temp; hLoop++)
- {
- UInt32 index;
-
- index = GetGridValue (indexH, indexV);
- fTiles->CopyImageClipped (index, drawY, drawX);
- indexH++;
- drawX +=32;
- }
-
- indexV++;
- drawY += 32;
- destRowPtr += vertOffset;
- }
-
-
- for (vLoop = 0; vLoop < tilesHeight; vLoop++)
- {
-
- drawX = startX;
- indexH = startH;
- destPtr = destRowPtr;
-
- if (horizontalClip)
- {
- UInt32 index;
-
- index = GetGridValue (indexH, indexV);
- fTiles->CopyImageClipped (index, drawY, drawX);
- indexH++;
- drawX +=32;
- destPtr += 32;
- }
-
- for (hLoop = 0; hLoop < tilesWidth; hLoop++)
- {
- UInt32 index;
-
- index = GetGridValue (indexH, indexV);
-
- // *********** NOTE: *************
- // Alignment is actually the same for all tiles in the grid, which means we could
- // optimize this switch outside all the loops. Only problem with this is that we
- // end up copying a lot of loop code for each alignment. I had this in the code and
- // took it out for this sample because the function had grown huge, but it is something
- // that can be optimized further. In a C implementation, you could probably get a function
- // pointer to the appropriate blitter, once, outside the loop and call from inside.
- switch (alignment)
- {
- case 0: fTiles->CopyImageUnclipped0 (index, destPtr); break;
- case 1: fTiles->CopyImageUnclipped1 (index, destPtr); break;
- case 2: fTiles->CopyImageUnclipped2 (index, destPtr); break;
- case 3: fTiles->CopyImageUnclipped3 (index, destPtr); break;
- case 4: fTiles->CopyImageUnclipped4 (index, destPtr); break;
- case 5: fTiles->CopyImageUnclipped5 (index, destPtr); break;
- case 6: fTiles->CopyImageUnclipped6 (index, destPtr); break;
- case 7: fTiles->CopyImageUnclipped7 (index, destPtr); break;
- }
- indexH++;
- drawX +=32;
- destPtr += 32;
- }
-
- if (horizontalClip)
- {
- UInt32 index;
-
- index = GetGridValue (indexH, indexV);
- fTiles->CopyImageClipped (index, drawY, drawX);
- }
-
- indexV++;
- drawY += 32;
- destRowPtr += vertOffset;
- }
-
- // handle bottom clip
- if (verticalClip)
- {
- SInt32 temp;
-
- drawX = startX;
- indexH = startH;
-
- if (horizontalClip)
- temp = tilesWidth+2;
- else
- temp = tilesWidth;
-
- for (hLoop = 0; hLoop < temp; hLoop++)
- {
- UInt32 index;
-
- index = GetGridValue (indexH, indexV);
- fTiles->CopyImageClipped (index, drawY, drawX);
- indexH++;
- drawX +=32;
- }
- }
- }